import {ChainStore} from "bitsharesjs";
import Immutable from "immutable";

let KeyAuth = function(auth) {
    this.id = auth.toJS ? auth.get(0) : auth[0];
    this.weight = auth.toJS ? auth.get(1) : auth[1];

    this.isAvailable = auths => {
        return auths.includes
            ? auths.includes(this.id)
            : auths.indexOf(this) !== -1;
    };
};

let permissionUtils = {
    AccountPermission: function(account, weight, type) {
        this.id = account.get("id");
        this.weight = weight;
        this.threshold = account.getIn([type, "weight_threshold"]);
        this.accounts = [];
        this.keys = account
            .getIn([type, "key_auths"])
            .map(auth => {
                return new KeyAuth(auth);
            })
            .toArray();

        this.isAvailable = auths => {
            return auths.includes
                ? auths.includes(this.id)
                : auths.indexOf(this) !== -1;
        };

        this._sumWeights = auths => {
            if (!this.isNested() && !this.isMultiSig()) {
                return this.isAvailable(auths) ? this.weight : 0;
            } else {
                let sum = this.accounts.reduce((status, account) => {
                    return (
                        status +
                        (account._sumWeights(auths) ? account.weight : 0)
                    );
                }, 0);

                return Math.floor(sum / this.threshold);
            }
        };

        this.getStatus = (auths, keyAuths) => {
            if (!this.isNested()) {
                let sum = this._sumWeights(auths);
                if (this.isMultiSig()) {
                    sum += this.sumKeys(keyAuths);
                }
                return sum;
            } else {
                let sum = this.accounts.reduce((status, account) => {
                    return status + account._sumWeights(auths);
                }, 0);

                if (this.keys.length) {
                    sum += this.sumKeys(keyAuths);
                }

                return sum;
            }
        };

        this.sumKeys = keyAuths => {
            let keySum = this.keys.reduce((s, key) => {
                return s + (key.isAvailable(keyAuths) ? key.weight : 0);
            }, 0);
            return keySum;
        };

        this.isNested = () => {
            return this.accounts.length > 0;
        };

        this.isMultiSig = () => {
            return this.keys.reduce((final, key) => {
                return final || key.weight < this.threshold;
            }, false);
        };

        this.getMissingSigs = auths => {
            let missing = [];
            let nested = [];
            if (this.isNested()) {
                nested = this.accounts.reduce((a, account) => {
                    return a.concat(account.getMissingSigs(auths));
                }, []);
            } else if (!this.isAvailable(auths)) {
                missing.push(this.id);
            }

            return missing.concat(nested);
        };

        this.getMissingKeys = auths => {
            let missing = [];
            let nested = [];
            if (this.keys.length && (this.isNested() || this.isMultiSig())) {
                this.keys.forEach(key => {
                    if (!key.isAvailable(auths)) {
                        missing.push(key.id);
                    }
                });
            }

            if (this.isNested()) {
                nested = this.accounts.reduce((a, account) => {
                    return a.concat(account.getMissingKeys(auths));
                }, []);
            }

            return missing.concat(nested);
        };
    },

    listToIDs: function(accountList) {
        let allAccounts = [];

        accountList.forEach(account => {
            if (account) {
                allAccounts.push(account.get ? account.get("id") : account);
            }
        });

        return allAccounts;
    },

    unravel: function(accountPermission, type, recursive_count = 0) {
        if (recursive_count < 3) {
            let account = ChainStore.getAccount(accountPermission.id);
            if (account && account.getIn([type, "account_auths"]).size) {
                account.getIn([type, "account_auths"]).forEach(auth => {
                    let nestedAccount = ChainStore.getAccount(auth.get(0));
                    if (nestedAccount) {
                        accountPermission.accounts.push(
                            this.unravel(
                                new this.AccountPermission(
                                    nestedAccount,
                                    auth.get(1),
                                    type
                                ),
                                type,
                                recursive_count + 1
                            )
                        );
                    }
                });
            }
        }

        return accountPermission;
    },

    unnest: function(accounts, type) {
        let map = [];
        accounts.forEach(id => {
            let fullAccount = ChainStore.getAccount(id);
            if (!fullAccount) return;
            let currentPermission = this.unravel(
                new this.AccountPermission(fullAccount, null, type),
                type
            );
            map.push(currentPermission);
        });

        return map;
    },

    flatten_auths(auths, existingAuths = Immutable.List()) {
        if (!auths.size) {
            return existingAuths;
        }

        auths.forEach(owner => {
            if (!existingAuths.includes(owner.get(0))) {
                existingAuths = existingAuths.push(owner.get(0));
            }
        });
        return existingAuths;
    }
};

export default permissionUtils;
